/**
 * @license Highcharts JS v3.0.10 (2014-03-10) Exporting module
 * 
 * (c) 2010-2014 Torstein Honsi
 * 
 * License: www.highcharts.com/license
 */

// JSLint options:
/* global Highcharts, document, window, Math, setTimeout */

(function(Highcharts) { // encapsulate

	// create shortcuts
	var Chart = Highcharts.Chart, addEvent = Highcharts.addEvent, removeEvent = Highcharts.removeEvent, createElement = Highcharts.createElement, discardElement = Highcharts.discardElement, css = Highcharts.css, merge = Highcharts.merge, each = Highcharts.each, extend = Highcharts.extend, math = Math, mathMax = math.max, doc = document, win = window, isTouchDevice = Highcharts.isTouchDevice, M = 'M', L = 'L', DIV = 'div', HIDDEN = 'hidden', NONE = 'none', PREFIX = 'highcharts-', ABSOLUTE = 'absolute', PX = 'px', UNDEFINED, symbols = Highcharts.Renderer.prototype.symbols, defaultOptions = Highcharts
			.getOptions(), buttonOffset;

	// Add language
	extend(defaultOptions.lang, {
		printChart : 'Print chart',
		downloadPNG : 'Download PNG image',
		downloadJPEG : 'Download JPEG image',
		downloadPDF : 'Download PDF document',
		downloadSVG : 'Download SVG vector image',
		contextButtonTitle : 'Chart context menu'
	});

	// Buttons and menus are collected in a separate config option set called
	// 'navigation'.
	// This can be extended later to add control buttons like zoom and pan right
	// click menus.
	defaultOptions.navigation = {
		menuStyle : {
			border : '1px solid #A0A0A0',
			background : '#FFFFFF',
			padding : '5px 0'
		},
		menuItemStyle : {
			padding : '0 10px',
			background : NONE,
			color : '#303030',
			fontSize : isTouchDevice ? '14px' : '11px'
		},
		menuItemHoverStyle : {
			background : '#4572A5',
			color : '#FFFFFF'
		},

		buttonOptions : {
			symbolFill : '#E0E0E0',
			symbolSize : 14,
			symbolStroke : '#666',
			symbolStrokeWidth : 3,
			symbolX : 12.5,
			symbolY : 10.5,
			align : 'right',
			buttonSpacing : 3,
			height : 22,
			// text: null,
			theme : {
				fill : 'white', // capture hover
				stroke : 'none'
			},
			verticalAlign : 'top',
			width : 24
		}
	};

	// Add the export related options
	defaultOptions.exporting = {
		// enabled: true,
		// filename: 'chart',
		type : 'image/png',
		url : 'http://export.highcharts.com/',
		// width: undefined,
		// scale: 2
		buttons : {
			contextButton : {
				menuClassName : PREFIX + 'contextmenu',
				// x: -10,
				symbol : 'menu',
				_titleKey : 'contextButtonTitle',
				menuItems : [ {
					textKey : 'printChart',
					onclick : function() {
						this.print();
					}
				}, {
					separator : true
				}, {
					textKey : 'downloadPNG',
					onclick : function() {
						this.exportChart();
					}
				}, {
					textKey : 'downloadJPEG',
					onclick : function() {
						this.exportChart({
							type : 'image/jpeg'
						});
					}
				}, {
					textKey : 'downloadPDF',
					onclick : function() {
						this.exportChart({
							type : 'application/pdf'
						});
					}
				}, {
					textKey : 'downloadSVG',
					onclick : function() {
						this.exportChart({
							type : 'image/svg+xml'
						});
					}
				}
				// Enable this block to add "View SVG" to the dropdown menu
				/*
				 * ,{
				 * 
				 * text: 'View SVG', onclick: function () { var svg =
				 * this.getSVG() .replace(/</g, '\n&lt;') .replace(/>/g,
				 * '&gt;');
				 * 
				 * doc.body.innerHTML = '<pre>' + svg + '</pre>'; } } //
				 */
				]
			}
		}
	};

	// Add the Highcharts.post utility
	Highcharts.post = function(url, data, formAttributes , callback) {
		var name, form;
		
		return $.post(url , data , function(img){
			callback && callback(img);
		});

		// create the form
		form = createElement('form', merge({
			method : 'post',
			action : url,
			target : '_blank',
			enctype : 'application/x-www-form-urlencoded'
		}, formAttributes), {
			display : NONE
		}, doc.body);

		// add the data
		for (name in data) {
			createElement('input', {
				type : HIDDEN,
				name : name,
				value : data[name]
			}, null, form);
		}

		// submit
		form.submit();

		// clean up
		discardElement(form);
	};

	extend(
			Chart.prototype,
			{

				/**
				 * Return an SVG representation of the chart
				 * 
				 * @param additionalOptions
				 *            {Object} Additional chart options for the
				 *            generated SVG representation
				 */
				getSVG : function(additionalOptions) {
					var chart = this, chartCopy, sandbox, svg, seriesOptions, sourceWidth, sourceHeight, cssWidth, cssHeight, options = merge(
							chart.options, additionalOptions); // copy the
																// options and
																// add extra
																// options

					// IE compatibility hack for generating SVG content that it
					// doesn't really understand
					if (!doc.createElementNS) {
						/* jslint unparam: true *//*
												 * allow unused parameter ns in
												 * function below
												 */
						doc.createElementNS = function(ns, tagName) {
							return doc.createElement(tagName);
						};
						/* jslint unparam: false */
					}

					// create a sandbox where a new chart will be generated
					sandbox = createElement(DIV, null, {
						position : ABSOLUTE,
						top : '-9999em',
						width : chart.chartWidth + PX,
						height : chart.chartHeight + PX
					}, doc.body);

					// get the source size
					cssWidth = chart.renderTo.style.width;
					cssHeight = chart.renderTo.style.height;
					sourceWidth = options.exporting.sourceWidth
							|| options.chart.width
							|| (/px$/.test(cssWidth) && parseInt(cssWidth, 10))
							|| 600;
					sourceHeight = options.exporting.sourceHeight
							|| options.chart.height
							|| (/px$/.test(cssHeight) && parseInt(cssHeight, 10))
							|| 400;

					// override some options
					extend(options.chart, {
						animation : false,
						renderTo : sandbox,
						forExport : true,
						width : sourceWidth,
						height : sourceHeight
					});
					options.exporting.enabled = false; // hide buttons in print

					// prepare for replicating the chart
					options.series = [];
					each(chart.series, function(serie) {
						seriesOptions = merge(serie.options, {
							animation : false, // turn off animation
							showCheckbox : false,
							visible : serie.visible
						});

						if (!seriesOptions.isInternal) { // used for the
															// navigator series
															// that has its own
															// option set
							options.series.push(seriesOptions);
						}
					});

					// generate the chart copy
					chartCopy = new Highcharts.Chart(options, chart.callback);

					// reflect axis extremes in the export
					each(
							[ 'xAxis', 'yAxis' ],
							function(axisType) {
								each(
										chart[axisType],
										function(axis, i) {
											var axisCopy = chartCopy[axisType][i], extremes = axis
													.getExtremes(), userMin = extremes.userMin, userMax = extremes.userMax;

											if (axisCopy
													&& (userMin !== UNDEFINED || userMax !== UNDEFINED)) {
												axisCopy.setExtremes(userMin,
														userMax, true, false);
											}
										});
							});

					// get the SVG from the container's innerHTML
					svg = chartCopy.container.innerHTML;

					// free up memory
					options = null;
					chartCopy.destroy();
					discardElement(sandbox);

					// sanitize
					svg = svg.replace(/zIndex="[^"]+"/g, '').replace(
							/isShadow="[^"]+"/g, '').replace(
							/symbolName="[^"]+"/g, '').replace(
							/jQuery[0-9]+="[^"]+"/g, '').replace(
							/url\([^#]+#/g, 'url(#').replace(/<svg /,
							'<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
							.replace(/ href=/g, ' xlink:href=').replace(/\n/,
									' ').replace(/<\/svg>.*?$/, '</svg>') // any
																			// HTML
																			// added
																			// to
																			// the
																			// container
																			// after
																			// the
																			// SVG
																			// (#894)
							/*
							 * This fails in IE < 8
							 * .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2,
							 * s3) { // round off to save weight return s2 +'.'+
							 * s3[0]; })
							 */

							// Replace HTML entities, issue #347
							.replace(/&nbsp;/g, '\u00A0') // no-break space
							.replace(/&shy;/g, '\u00AD') // soft hyphen

							// IE specific
							.replace(/<IMG /g, '<image ').replace(
									/height=([^" ]+)/g, 'height="$1"').replace(
									/width=([^" ]+)/g, 'width="$1"').replace(
									/hc-svg-href="([^"]+)">/g,
									'xlink:href="$1"/>').replace(
									/id=([^" >]+)/g, 'id="$1"').replace(
									/class=([^" >]+)/g, 'class="$1"').replace(
									/ transform /g, ' ').replace(
									/:(path|rect)/g, '$1').replace(
									/style="([^"]+)"/g, function(s) {
										return s.toLowerCase();
									});

					// IE9 beta bugs with innerHTML. Test again with final IE9.
					svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
							.replace(/&quot;/g, "'");

					return svg;
				},

				/**
				 * Submit the SVG representation of the chart to the server
				 * 
				 * @param {Object}
				 *            options Exporting options. Possible members are
				 *            url, type, width and formAttributes.
				 * @param {Object}
				 *            chartOptions Additional chart options for the SVG
				 *            representation of the chart
				 */
				exportChart : function(options, chartOptions , callback) {
					options = options || {};

					var chart = this, chartExportingOptions = chart.options.exporting, svg = chart
							.getSVG(merge(
									{
										chart : {
											borderRadius : 0
										}
									},
									chartExportingOptions.chartOptions,
									chartOptions,
									{
										exporting : {
											sourceWidth : options.sourceWidth
													|| chartExportingOptions.sourceWidth,
											sourceHeight : options.sourceHeight
													|| chartExportingOptions.sourceHeight
										}
									}));

					// merge the options
					options = merge(chart.options.exporting, options);

					// do the post
					Highcharts.post(options.url, {
						filename : options.filename || 'chart',
						type : options.type,
						width : options.width || 0, // IE8 fails to post
													// undefined correctly, so
													// use 0
						scale : options.scale || 2,
						svg : svg
					}, options.formAttributes , callback);

				},

				/**
				 * Print the chart
				 */
				print : function() {

					var chart = this, container = chart.container, origDisplay = [], origParent = container.parentNode, body = doc.body, childNodes = body.childNodes;

					if (chart.isPrinting) { // block the button while in
											// printing mode
						return;
					}

					chart.isPrinting = true;

					// hide all body content
					each(childNodes, function(node, i) {
						if (node.nodeType === 1) {
							origDisplay[i] = node.style.display;
							node.style.display = NONE;
						}
					});

					// pull out the chart
					body.appendChild(container);

					// print
					win.focus(); // #1510
					win.print();

					// allow the browser to prepare before reverting
					setTimeout(function() {

						// put the chart back in
						origParent.appendChild(container);

						// restore all body content
						each(childNodes, function(node, i) {
							if (node.nodeType === 1) {
								node.style.display = origDisplay[i];
							}
						});

						chart.isPrinting = false;

					}, 1000);

				},

				/**
				 * Display a popup menu for choosing the export type
				 * 
				 * @param {String}
				 *            className An identifier for the menu
				 * @param {Array}
				 *            items A collection with text and onclicks for the
				 *            items
				 * @param {Number}
				 *            x The x position of the opener button
				 * @param {Number}
				 *            y The y position of the opener button
				 * @param {Number}
				 *            width The width of the opener button
				 * @param {Number}
				 *            height The height of the opener button
				 */
				contextMenu : function(className, items, x, y, width, height,
						button) {
					var chart = this, navOptions = chart.options.navigation, menuItemStyle = navOptions.menuItemStyle, chartWidth = chart.chartWidth, chartHeight = chart.chartHeight, cacheName = 'cache-'
							+ className, menu = chart[cacheName], menuPadding = mathMax(
							width, height), // for mouse leave detection
					boxShadow = '3px 3px 10px #888', innerMenu, hide, hideTimer, menuStyle, docMouseUpHandler = function(
							e) {
						if (!chart.pointer.inClass(e.target, className)) {
							hide();
						}
					};

					// create the menu only the first time
					if (!menu) {

						// create a HTML element above the SVG
						chart[cacheName] = menu = createElement(DIV, {
							className : className
						}, {
							position : ABSOLUTE,
							zIndex : 1000,
							padding : menuPadding + PX
						}, chart.container);

						innerMenu = createElement(DIV, null, extend({
							MozBoxShadow : boxShadow,
							WebkitBoxShadow : boxShadow,
							boxShadow : boxShadow
						}, navOptions.menuStyle), menu);

						// hide on mouse out
						hide = function() {
							css(menu, {
								display : NONE
							});
							if (button) {
								button.setState(0);
							}
							chart.openMenu = false;
						};

						// Hide the menu some time after mouse leave (#1357)
						addEvent(menu, 'mouseleave', function() {
							hideTimer = setTimeout(hide, 500);
						});
						addEvent(menu, 'mouseenter', function() {
							clearTimeout(hideTimer);
						});

						// Hide it on clicking or touching outside the menu
						// (#2258, #2335, #2407)
						addEvent(document, 'mouseup', docMouseUpHandler);
						addEvent(chart, 'destroy',
								function() {
									removeEvent(document, 'mouseup',
											docMouseUpHandler);
								});

						// create the items
						each(
								items,
								function(item) {
									if (item) {
										var element = item.separator ? createElement(
												'hr', null, null, innerMenu)
												: createElement(
														DIV,
														{
															onmouseover : function() {
																css(
																		this,
																		navOptions.menuItemHoverStyle);
															},
															onmouseout : function() {
																css(this,
																		menuItemStyle);
															},
															onclick : function() {
																hide();
																item.onclick
																		.apply(
																				chart,
																				arguments);
															},
															innerHTML : item.text
																	|| chart.options.lang[item.textKey]
														}, extend({
															cursor : 'pointer'
														}, menuItemStyle),
														innerMenu);

										// Keep references to menu divs to be
										// able to destroy them
										chart.exportDivElements.push(element);
									}
								});

						// Keep references to menu and innerMenu div to be able
						// to destroy them
						chart.exportDivElements.push(innerMenu, menu);

						chart.exportMenuWidth = menu.offsetWidth;
						chart.exportMenuHeight = menu.offsetHeight;
					}

					menuStyle = {
						display : 'block'
					};

					// if outside right, right align it
					if (x + chart.exportMenuWidth > chartWidth) {
						menuStyle.right = (chartWidth - x - width - menuPadding)
								+ PX;
					} else {
						menuStyle.left = (x - menuPadding) + PX;
					}
					// if outside bottom, bottom align it
					if (y + height + chart.exportMenuHeight > chartHeight
							&& button.alignOptions.verticalAlign !== 'top') {
						menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
					} else {
						menuStyle.top = (y + height - menuPadding) + PX;
					}

					css(menu, menuStyle);
					chart.openMenu = true;
				},

				/**
				 * Add the export button to the chart
				 */
				addButton : function(options) {
					var chart = this, renderer = chart.renderer, btnOptions = merge(
							chart.options.navigation.buttonOptions, options), onclick = btnOptions.onclick, menuItems = btnOptions.menuItems, symbol, button, symbolAttr = {
						stroke : btnOptions.symbolStroke,
						fill : btnOptions.symbolFill
					}, symbolSize = btnOptions.symbolSize || 12;
					if (!chart.btnCount) {
						chart.btnCount = 0;
					}

					// Keeps references to the button elements
					if (!chart.exportDivElements) {
						chart.exportDivElements = [];
						chart.exportSVGElements = [];
					}

					if (btnOptions.enabled === false) {
						return;
					}

					var attr = btnOptions.theme, states = attr.states, hover = states
							&& states.hover, select = states && states.select, callback;

					delete attr.states;

					if (onclick) {
						callback = function() {
							onclick.apply(chart, arguments);
						};

					} else if (menuItems) {
						callback = function() {
							chart.contextMenu(button.menuClassName, menuItems,
									button.translateX, button.translateY,
									button.width, button.height, button);
							button.setState(2);
						};
					}

					if (btnOptions.text && btnOptions.symbol) {
						attr.paddingLeft = Highcharts
								.pick(attr.paddingLeft, 25);

					} else if (!btnOptions.text) {
						extend(attr, {
							width : btnOptions.width,
							height : btnOptions.height,
							padding : 0
						});
					}

					button = renderer.button(btnOptions.text, 0, 0, callback,
							attr, hover, select).attr({
						title : chart.options.lang[btnOptions._titleKey],
						'stroke-linecap' : 'round'
					});
					button.menuClassName = options.menuClassName || PREFIX
							+ 'menu-' + chart.btnCount++;

					if (btnOptions.symbol) {
						symbol = renderer
								.symbol(btnOptions.symbol,
										btnOptions.symbolX - (symbolSize / 2),
										btnOptions.symbolY - (symbolSize / 2),
										symbolSize, symbolSize)
								.attr(
										extend(
												symbolAttr,
												{
													'stroke-width' : btnOptions.symbolStrokeWidth || 1,
													zIndex : 1
												})).add(button);
					}

					button.add().align(extend(btnOptions, {
						width : button.width,
						x : Highcharts.pick(btnOptions.x, buttonOffset)
					// #1654
					}), true, 'spacingBox');

					buttonOffset += (button.width + btnOptions.buttonSpacing)
							* (btnOptions.align === 'right' ? -1 : 1);

					chart.exportSVGElements.push(button, symbol);

				},

				/**
				 * Destroy the buttons.
				 */
				destroyExport : function(e) {
					var chart = e.target, i, elem;

					// Destroy the extra buttons added
					for (i = 0; i < chart.exportSVGElements.length; i++) {
						elem = chart.exportSVGElements[i];

						// Destroy and null the svg/vml elements
						if (elem) { // #1822
							elem.onclick = elem.ontouchstart = null;
							chart.exportSVGElements[i] = elem.destroy();
						}
					}

					// Destroy the divs for the menu
					for (i = 0; i < chart.exportDivElements.length; i++) {
						elem = chart.exportDivElements[i];

						// Remove the event handler
						removeEvent(elem, 'mouseleave');

						// Remove inline events
						chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;

						// Destroy the div by moving to garbage bin
						discardElement(elem);
					}
				}
			});

	symbols.menu = function(x, y, width, height) {
		var arr = [ M, x, y + 2.5, L, x + width, y + 2.5, M, x,
				y + height / 2 + 0.5, L, x + width, y + height / 2 + 0.5, M, x,
				y + height - 1.5, L, x + width, y + height - 1.5 ];
		return arr;
	};

	// Add the buttons on chart load
	Chart.prototype.callbacks
			.push(function(chart) {
				var n, exportingOptions = chart.options.exporting, buttons = exportingOptions.buttons;

				buttonOffset = 0;

				if (exportingOptions.enabled !== false) {

					for (n in buttons) {
						chart.addButton(buttons[n]);
					}

					// Destroy the export elements at chart destroy
					addEvent(chart, 'destroy', chart.destroyExport);
				}

			});

}(Highcharts));
